format ELF64 executable 3
entry start

macro vec_shuf d*,s*,z*,y*,x*,w* {
    shufps d,s,(z shl 6) or (y shl 4) or (x shl 2) or w
}

segment readable executable

malloc:
    mov eax,9           ; sys_mmap
    xor esi,esi         ; addr
    xchg esi,edi        ; length (esi)
    mov edx,0x1+0x2     ; prot = PROT_READ | PROT_WRITE
    mov r10d,0x02+0x20  ; flags = MAP_PRIVATE | MAP_ANONYMOUS
    mov r8,-1           ; fd
    xor r9d,r9d         ; offset
    syscall
    ret

; in: vector in xmm0.xyz
; out: vector length replicated in xmm0.xyzw
length3:
    mulps xmm0,xmm0
    movaps xmm1,xmm0
    movaps xmm2,xmm0
    shufps xmm0,xmm0,0x00
    shufps xmm1,xmm1,0x55
    shufps xmm2,xmm2,0xaa
    addps xmm0,xmm1
    addps xmm0,xmm2
    sqrtps xmm0,xmm0
    ret

normalize3:
    movaps xmm3,xmm0
    mulps xmm0,xmm0
    movaps xmm1,xmm0
    movaps xmm2,xmm0
    shufps xmm0,xmm0,0x00
    shufps xmm1,xmm1,0x55
    shufps xmm2,xmm2,0xaa
    addps xmm0,xmm1
    addps xmm0,xmm2
    rsqrtps xmm0,xmm0
    mulps xmm0,xmm3
    ret

cross:
    movaps xmm2,xmm0
    movaps xmm3,xmm1
    vec_shuf xmm0,xmm0,3,0,2,1
    vec_shuf xmm1,xmm1,3,1,0,2
    vec_shuf xmm2,xmm2,3,1,0,2
    vec_shuf xmm3,xmm3,3,0,2,1
    mulps xmm0,xmm1
    mulps xmm2,xmm3
    subps xmm0,xmm2
    ret

proc_ret:
    mov rsp,rbp
    pop rbp
    ret

map:
    call length3
    subps xmm0,dqword [g_1p0]
    ret

; in: ray origin in xmm0.xyz
; in: ray direction in xmm1.xyz
; out: distance to the nearest object replicated in xmm0.xyzw or -1.0 if there is no hit
cast_ray:
    push rbp
    mov rbp,rsp
    sub rsp,128
    ro equ rbp-16
    rd equ rbp-32
    t equ rbp-48
    ;;; set stack variables
    movaps [ro],xmm0
    movaps [rd],xmm1
    xorps xmm0,xmm0
    movaps [t],xmm0
      .march:
    ;;; find distance to the nearest object
    movaps xmm0,[rd]
    mulps xmm0,[t]
    addps xmm0,[ro]
    call map
    ;;; return if distance is less than 0.001
    movaps xmm1,xmm0
    cmpltps xmm1,dqword [g_hit_dist]
    movmskps eax,xmm1
    cmp eax,15
    je .hit
    ;;; increment t with distance to the nearest object
    movaps xmm1,[t]
    addps xmm1,xmm0
    movaps [t],xmm1
    ;;; continue loop only if distance is less than 40.0
    cmpltps xmm1,dqword [g_max_dist]
    movmskps eax,xmm1
    cmp eax,15
    je .march
    xorps xmm0,xmm0
    subps xmm0,dqword [g_1p0]
    movaps [t],xmm0
      .hit:
    movaps xmm0,[t]
    jmp proc_ret
    restore ro,rd,t

compute_color:
    push rbp
    mov rbp,rsp
    sub rsp,128
    rgba equ rbp-8
    vx equ rbp-32
    vy equ rbp-48
    ;;; save function parameters on the stack
    shufps xmm0,xmm0,0x00
    shufps xmm1,xmm1,0x00
    mov [rgba],rdi
    movaps [vx],xmm0
    movaps [vy],xmm1
    ;;; compute Z axis
    xorps xmm0,xmm0
    subps xmm0,dqword [g_cam_pos]
    call normalize3
    movaps xmm7,xmm0
    ;;; compute X axis
    movaps xmm0,dqword [g_unit_y]
    movaps xmm1,xmm7
    call cross
    call normalize3
    movaps xmm6,xmm0
    ;;; compute Y axis
    movaps xmm0,xmm7
    movaps xmm1,xmm6
    call cross
    call normalize3
    ;;; compute ray direction
    mulps xmm0,[vy]
    mulps xmm6,[vx]
    movaps xmm1,xmm7
    mulps xmm1,dqword [g_0p5]
    addps xmm7,xmm1
    addps xmm0,xmm6
    addps xmm0,xmm7
    call normalize3
    ;;; cast ray
    movaps xmm1,xmm0
    movaps xmm0,dqword [g_cam_pos]
    call cast_ray
    cmpnltps xmm0,dqword [g_0p0]
    movaps xmm1,dqword [g_1p0]
    andps xmm1,xmm0
    mov rdi,[rgba]
    movaps [rdi],xmm1
    jmp proc_ret
    restore rgba,vx,vy

start:
    mov rbp,rsp
    sub rsp,128
    img equ rbp-8
    img8 equ rbp-16
    ix equ rbp-20
    iy equ rbp-24
    ;---------------------------------------------------------------------------
    ; generate floating point image
    ;---------------------------------------------------------------------------
    ;;; alloc memory
    mov edi,IMG_WIDTH*IMG_HEIGHT*16
    call malloc
    mov [img],rax
    mov rbx,[img]
    ;;; begin loops
    mov dword [iy],0
      .for_each_y:
    mov dword [ix],0
      .for_each_x:
    ;;; set pixel address
    mov rdi,rbx
    ;;; compute x coordinate [-1.777 , 1.777]
    cvtsi2ss xmm0,[ix]
    divss xmm0,[g_img_width]
    subss xmm0,[g_0p5]
    addss xmm0,xmm0
    mov eax,1.777
    movd xmm2,eax
    mulss xmm0,xmm2
    ;;; compute y coordinate [-1.0 , 1.0]
    cvtsi2ss xmm1,[iy]
    divss xmm1,[g_img_height]
    subss xmm1,[g_0p5]
    addss xmm1,xmm1
    ;;; compute pixel color
    call compute_color
    ;;; advance pixel pointer
    add rbx,16
    ;;; continue for_each_x loop
    inc dword [ix]
    cmp dword [ix],IMG_WIDTH
    jne .for_each_x
    ;;; continue for_each_y loop
    inc dword [iy]
    cmp dword [iy],IMG_HEIGHT
    jne .for_each_y
    ;---------------------------------------------------------------------------
    ; convert image to 4 x 8 bpp
    ;---------------------------------------------------------------------------
    ;;; alloc memory
    mov edi,IMG_WIDTH*IMG_HEIGHT*4
    call malloc
    mov [img8],rax
    ;;; set dst and src pointers and loop counter
    mov rdi,[img8]
    mov rsi,[img]
    mov ecx,IMG_WIDTH*IMG_HEIGHT
      @@:
    ;;; clamp to [0.0 , 1.0]
    movaps xmm0,[rsi]
    maxps xmm0,dqword [g_0p0]
    minps xmm0,dqword [g_1p0]
    ;;; convert from [0.0 , 1.0] to [0 , 255]
    mulps xmm0,dqword [g_255p0]
    cvttps2dq xmm0,xmm0
    pshufb xmm0,dqword [g_image_conv_mask]
    ;;; set alpha to 0xff
    movd [rdi],xmm0
    or dword [rdi],0xff000000
    ;;; continue loop
    add rsi,16
    add rdi,4
    dec ecx
    jnz @b
    ;---------------------------------------------------------------------------
    ; write image to TGA file
    ;---------------------------------------------------------------------------
    ;;; create file
    mov eax,85
    mov rdi,g_img_name
    mov esi,110000000b
    syscall
    mov rbx,rax
    ;;; write header
    mov eax,1
    mov rdi,rbx
    mov rsi,g_img_header
    mov edx,18
    syscall
    ;;; write pixel data
    mov eax,1
    mov rdi,rbx
    mov rsi,[img8]
    mov edx,IMG_WIDTH*IMG_HEIGHT*4
    syscall
    ;;; terminate process
    mov eax,60
    xor edi,edi
    syscall
    restore img,img8,ix,iy

align 1
g_img_name db 'skeleton.tga',0
g_img_header db 0,0,2,9 dup 0,(IMG_WIDTH and 0x00ff),(IMG_WIDTH and 0xff00) shr 8,(IMG_HEIGHT and 0x00ff),(IMG_HEIGHT and 0xff00) shr 8,32,0

align 4
IMG_WIDTH = 1280
IMG_HEIGHT = 720
g_img_width dd 1280.0
g_img_height dd 720.0

align 16
g_image_conv_mask db 8,4,0,12,12 dup 0x80
g_0p0 dd 4 dup 0.0
g_0p5 dd 4 dup 0.5
g_1p0 dd 4 dup 1.0
g_255p0 dd 4 dup 255.0
g_cam_pos dd 0.0,0.0,3.0,1.0
g_unit_y dd 0.0,1.0,0.0,0.0
g_hit_dist dd 4 dup 0.001
g_max_dist dd 4 dup 10.0
